package utils

import (
	"context"
	"errors"
	"math"
	"time"

	"gitee.com/liuyun-luo/MagicStreamMovies/Server/config"
	"gitee.com/liuyun-luo/MagicStreamMovies/Server/models"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
	"golang.org/x/crypto/bcrypt"
)

// 加密密码
func HashPassword(pwd string) (string, error) {
	hash, err := bcrypt.GenerateFromPassword([]byte(pwd), 12)
	return string(hash), err
}

// 对比用户提交的密码和存储在数据库中加密之后的是否相等
func CheckPassword(input_pwd string, db_pwd string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(db_pwd), []byte(input_pwd))
	return err == nil
}

type SignedDetails struct {
	Email                string
	Name                 string
	Role                 string
	UserId               string
	jwt.RegisteredClaims //组合
}

// 生成JWT
func GenerateJWT(userId, email, name, role string) (jwtToken string, refreshToken string, err error) {
	claims := &SignedDetails{
		Email:  email,
		Name:   name,
		Role:   role,
		UserId: userId,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "MagicStream",                                      //令牌颁发者
			IssuedAt:  jwt.NewNumericDate(time.Now()),                     //令牌生效时间
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), //令牌失效时间
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	SignedToken, err := token.SignedString([]byte(config.SECRET_KEY))
	if err != nil {
		return "", "", err
	}

	//刷新令牌
	refresh_claims := &SignedDetails{
		Email:  email,
		Name:   name,
		Role:   role,
		UserId: userId,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "MagicStream",                                          //令牌颁发者
			IssuedAt:  jwt.NewNumericDate(time.Now()),                         //令牌生效时间
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * 7 * time.Hour)), //令牌失效时间
		},
	}
	refresh_token := jwt.NewWithClaims(jwt.SigningMethodHS256, refresh_claims)
	refresh_SignedToken, err := refresh_token.SignedString([]byte(config.SECRET_REFRESH_KEY))
	if err != nil {
		return "", "", err
	}

	return SignedToken, refresh_SignedToken, nil
}

// 获取HTTP报头中Authorization
func GetAccessToken(c *gin.Context) (string, error) {
	authHeader := c.Request.Header.Get("Authorization")
	if authHeader == "" {
		return "", errors.New("没有Authorization字段")
	}

	tokenString := authHeader[len("Bearer "):]

	if tokenString == "" {
		return "", errors.New("bearer token is required")
	}

	/*tokenString, err := c.Cookie("access_token")
	if err != nil {

		return "", err
	}*/

	return tokenString, nil
}

// 验证token
func ValidateToken(tokenString string) (*SignedDetails, error) {
	claims := &SignedDetails{}

	//把Token字符串反序列化为Token结构体对象
	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte(config.SECRET_KEY), nil
	})
	if err != nil {
		return nil, err
	}

	//检查使用的哈希算法
	if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
		return nil, err
	}

	//检查令牌是否过期
	if claims.ExpiresAt.Time.Before(time.Now()) {
		return nil, errors.New("令牌已过期")
	}

	return claims, nil
}

// 获取中间件设置的userId
func GetUserIdFromContext(c *gin.Context) (string, error) {
	user_id, ok := c.Get("userId")
	if !ok {
		return "", errors.New("userId不存在于该上下文中")
	}
	id, ok := user_id.(string)
	if !ok {
		return "", errors.New("该上下文中的userId不是string类型")
	}

	return id, nil
}

// 获取中间件设置的role
func GetRoleFromContext(c *gin.Context) (string, error) {
	ro, ok := c.Get("role")
	if !ok {
		return "", errors.New("role不存在于该上下文中")
	}

	role, ok := ro.(string)
	if !ok {
		return "", errors.New("该上下文中的role不是string类型")
	}

	return role, nil
}

// 根据userId获取用户喜欢的电影类型
func GetUserFavoriteMovieGenre(user_id string, ctx *gin.Context) ([]string, error) {
	// 连接到具体的数据库的集合
	collection, err := config.OpenCollection("users")
	if err != nil {
		return nil, err
	}

	cont, canel := context.WithTimeout(ctx, config.DefaultMongoDBOperationTimeout)
	defer canel()

	filter := bson.M{"user_id": user_id}

	projection := bson.M{
		"favourite_genres.genre_name": 1,
		"_id":                         0,
	}

	opts := options.FindOne().SetProjection(projection)
	var result bson.M

	if err := collection.FindOne(cont, filter, opts).Decode(&result); err != nil {
		if err == mongo.ErrNoDocuments { //如果数据库中没有记载用户喜欢的类型
			return []string{}, nil
		} else {
			return nil, err
		}
	}

	favGenresArray, ok := result["favourite_genres"].(bson.A)
	if !ok {
		return nil, errors.New("无法获取用户喜欢的电影类型")
	}

	var genreNames []string

	for _, item := range favGenresArray {
		if genreMap, ok := item.(bson.D); ok {
			for _, elem := range genreMap {
				if elem.Key == "genre_name" {
					if name, ok := elem.Value.(string); ok {
						genreNames = append(genreNames, name)
					}
				}
			}
		}
	}

	return genreNames, nil
}

// CalculateCoolingScore 计算基于牛顿冷却定律的衰减后热度
// baseScore: 基础热度
// baseTime: 基准时间
// decayCoefficient: 衰减系数
func calculateCoolingScore(baseScore float64, baseTime time.Time, decayCoefficient float64) float64 {
	hoursPassed := time.Since(baseTime).Hours() //从基准时间到当前时间的分钟差值--时间差
	// 使用牛顿冷却定律公式
	return baseScore * math.Exp(-decayCoefficient*hoursPassed)
}

// 电影的热度计算--对应ZSet的分数
func GetMoviePopularity(movie *models.Movie) float64 {
	//影片热度 = (观看次数 * 权重A + 点赞量 * 权重B + 平均评分 * 权重C + 收藏次数 * 权重D+评论总数*权重E) * 时间衰减因子
	base_popularity := float64(movie.PlayCount)*config.PopularityPlayCountWeight +
		float64(movie.LikeCount)*config.PopularityLikeCountWeight +
		float64(movie.Rating)*config.PopularityRatingWeight +
		float64(movie.CollectCount)*config.PopularityCollectCountWeight +
		float64(movie.ReviewCount)*config.PopularityReviewCountWeight

	//获取电影发布的时间--以此为基准
	movieReleaseDate := movie.CreatedAt

	return calculateCoolingScore(base_popularity, movieReleaseDate, config.PopularityDecayRate)
}

